package com.fesine.batch.service.impl;

import com.fesine.batch.dto.BatchScheduleGroupDTO;
import com.fesine.batch.model.BatchSchedulePo;
import com.fesine.batch.service.BatchScheduleService;
import com.fesine.batch.service.BatchSchedulerBrokerService;
import com.fesine.commons.util.SpringContextUtil;
import org.quartz.*;
import org.quartz.impl.StdScheduler;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.*;

import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

@Service("batchScheduleBroker")
public class BatchSchedulerBrokerServiceImpl implements BatchSchedulerBrokerService {
    private final static Logger logger = LoggerFactory.getLogger(BatchSchedulerBrokerServiceImpl.class);

    private final static String TRIGGER_GROUP = "trigger_fesine";
    @Autowired
    private BatchScheduleService batchScheduleService;

    @Resource
    private StdScheduler stdScheduler;


    public List<BatchScheduleGroupDTO> listScheduledGroups() throws Exception {
        List<BatchScheduleGroupDTO> lt = new LinkedList<>();
        Set<TriggerKey> tkSet = stdScheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(TRIGGER_GROUP));
        for (TriggerKey tk : tkSet) {
            Trigger t = stdScheduler.getTrigger(tk);
            Date d = t.getNextFireTime();
            String groupId = t.getJobKey().getName();
            BatchScheduleGroupDTO sg = new BatchScheduleGroupDTO();
            sg.setGroupId(groupId);
            sg.setNextFireTime(d);
            sg.setPrevFireTime(t.getPreviousFireTime());
            sg.setStartTime(t.getStartTime());
            sg.setStopTime(t.getEndTime());
            lt.add(sg);
        }
        // 调用List的排序，返回按nextFireTime排序后的列表
        Collections.sort(lt);
        return lt;
    }


    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public Date schedule(String groupId, Integer delay) {
        return scheduleJob(groupId,delay,null);
    }



    @Override
    public Date schedule(String groupId, Date startTime) {
        return scheduleJob(groupId, null, startTime);
    }


    /**
     * 注册延时或定时启动任务
     * @param groupId 被注册的任务对象
     * @param delay 延时时间
     * @param startTime 定时时间
     * @return
     */
    private Date scheduleJob(String groupId,Integer delay,Date startTime) {
        BatchSchedulePo bso = new BatchSchedulePo();
        bso.setGroupId(groupId);
        bso = batchScheduleService.get(bso);
        if (bso == null) {
            throw new RuntimeException(String.format("必须为%s这个任务组配置同名的定时任务", groupId));
        }
        if (startTime == null) {
            startTime = bso.getStartTime();
        }
        Date stopTime = bso.getStopTime();
        String cronExpression = bso.getCronExpression();
        // Long delay = bso.get;
        //根据groupId获取bean
        Object obj;
        try {
            obj = SpringContextUtil.getBean(groupId);
        } catch (NoSuchBeanDefinitionException e) {
            logger.error(String.format("找不到%s spring bean", groupId));
            throw e;
        }
        Class jobClazz = obj.getClass();
        if (!StringUtils.isEmpty(bso.getQuartzJobClass())) {
            try {
                jobClazz = Class.forName(bso.getQuartzJobClass());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(String.format("找不到配置的QuartzJobClass：%s", bso.getQuartzJobClass()));
            }
        }
        //
        JobDataMap jdm = new JobDataMap();
        jdm.put("fesineSchedule", bso);
        JobDetail jobDetail = newJob(jobClazz).withIdentity(groupId).usingJobData(jdm).storeDurably().build();
        TriggerBuilder tb = newTrigger().withIdentity(bso.getId(), TRIGGER_GROUP);
        if (!StringUtils.isEmpty(cronExpression)) {
            tb.withSchedule(cronSchedule(cronExpression));
        }

        if (startTime != null) {
            // 如果startTime小于当前系统日期,会立即执行一次，即使当前不满足cronExpression
            if (startTime.compareTo(new Date()) > 0) {
                tb.startAt(startTime);
            } else {
                tb.startNow();
            }
        } else if (delay != null && delay !=0) {
            startTime = new Date(new Date().getTime() + 1000L * delay);
            tb.startAt(startTime);
        }

        if (stopTime != null) {
            if (stopTime.compareTo(startTime) > 0) {
                tb.endAt(stopTime);
            }
        }
        try {
            if (stdScheduler.checkExists(jobDetail.getKey())) {
                stdScheduler.deleteJob(jobDetail.getKey());
            }
            // scheduler.addJob(jobDetail, true);
            Date rd = stdScheduler.scheduleJob(jobDetail, tb.build());
            return rd;
        } catch (SchedulerException e) {
            logger.error("Can not schedule {}, key: {}", groupId, jobDetail.getKey(), e);
            throw new RuntimeException(String.format("为任务组%s添加计划任务失败", groupId));
        }
    }


    @Override
    public boolean unschedule(String groupId) {
        try {
            stdScheduler.deleteJob(new JobKey(groupId));
        } catch (SchedulerException e) {
            logger.error("Can not stop schedule of job group {}", groupId, e);
        }
        return false;
    }


    @Override
    public boolean startScheduler() {
        try {
            this.stopScheduler();
            stdScheduler.start();
            return true;
        } catch (SchedulerException e) {
            logger.error("Can not start batch scheduler.", e);
        }
        return false;
    }


    @Override
    public boolean stopScheduler() {
        if (stdScheduler.isInStandbyMode()) {
            return true;
        }
        stdScheduler.standby();
        return true;
    }

    //public void setStdScheduler(StdScheduler stdScheduler) {
    //    this.stdScheduler = stdScheduler;
    //}
}